﻿/*=========================================================================
 * AUTH: Vincent Breton
 * DESC: Enemy Class
 *=========================================================================*/
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;   //   for Texture2D
using Microsoft.Xna.Framework;  //  for Vector2
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace RisingThreat
{
    class Enemy
    {
        public Steering steer { get; set; }
        private Model enemy;
        private EnemyStateMachine stateMachine;
        public PathFollowing path;
        private Seek seek;
        private Align align;       
        public bool isAlive = false;
        List<Teammate> team;
        Player player;

        public BoundingSphere bounding;

        public Enemy(Model e, Vector3 position, List<Teammate> t, Player p, LevelGraph g)
        {
            enemy = e;
            steer = new Steering(position, new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0f, 0f, 0f), 0, 0, 0, new Vector3(1.5f, 0, 1.5f), new Vector3(5.0f, 0.0f, 5.0f), 40.0f, 20.0f);
            path = new PathFollowing(g);
            seek = new Seek();
            align = new Align();
            stateMachine = new EnemyStateMachine(this);
            team = t;
            player = p;

            bounding.Center = steer.position+Vector3.Up;
            bounding.Radius = 0.5f; //random
        }

        public void update(GameTime elapsedTime, Level level)
        {
            if (isAlive)
            {
                stateMachine.update();
                move(elapsedTime);
                CheckForCollisions(level);
            }
        }


        public void move(GameTime elapsedTime)
        {
            float timeDifference = (float)elapsedTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;
            if (stateMachine.currentState == EnemyStateMachine.State.SEARCH)
            {
                /* if no path yet find one or if path is over find a new path*/
                if (path.currentIndex == -1)
                {   //interruptible path finding?
                    path.startPosition = steer.position;
                    path.goalPosition = pickClosestTarget();
                    path.graph.clearNodes();
                    path.findPath();                    
                    //path.printPath();
                }
                steer = path.getSteering(steer);
                updatePosition(timeDifference);
                steer = align.getSteering(steer);
                updateOrientation(timeDifference);
                bounding.Center = steer.position;
                bounding.Center.Y = 1.0f;
                steer.position.Y = 0.0f;//Y value clamp
            }

            else//currentState==ATTACK
            {
                seek.target = pickClosestTarget();
                steer = seek.getSteering(steer);
                updatePosition(timeDifference);
                steer = align.getSteering(steer);
                updateOrientation(timeDifference);
                bounding.Center = steer.position;
                bounding.Center.Y = 1.0f;
            }


        }

        public void CheckForCollisions(Level level)
        {
            CollisionDetector cd = new CollisionDetector();
            HashSet<QuadTreeItem> hashie = level.getPotentialCollisions(bounding);

            List<QuadTreeItem> collisions = cd.detectLevelCollisions(bounding, hashie);
            foreach (QuadTreeItem item in collisions)
            {

                if (bounding.Intersects(item.getBoundingVolume()))
                {
                    ResolveCollisionBVH(item);
                }
            }
        }

        public void ResolveCollisionBVH(QuadTreeItem item)
        {
            BoundingBox itemBound = new BoundingBox(item.getBoundingVolume().Min, item.getBoundingVolume().Max);
            Vector3 itemBoundCenter = new Vector3((itemBound.Min.X + itemBound.Max.X) / 2, 1.5f, (itemBound.Min.Z + itemBound.Max.Z) / 2);

            if ((this.steer.position.X > (itemBound.Min.X - this.bounding.Radius / 2)) && this.steer.position.X < (itemBound.Max.X + this.bounding.Radius / 2))
            {
                if (this.steer.position.Z > itemBoundCenter.Z)
                {
                    Vector3 contactNorm = new Vector3(0, 0, -1);
                    contactNorm.Normalize();
                    //contact position is implied with intersecting spheres
                    //calculate seperation velocity
                    Vector3 seperationVelocity = (this.steer.velocity);
                    seperationVelocity = seperationVelocity * contactNorm;
                    //use a coefficient of impulse
                    seperationVelocity *= 1.5f;
                    //apply impulse
                    this.steer.velocity += seperationVelocity;
                    //added code to make things smoother; so my code uses projection as well as impulses.
                    this.steer.position.Z = (itemBound.Max.Z + this.bounding.Radius);
                }
                else
                {
                    Vector3 contactNorm = new Vector3(0, 0, -1);
                    contactNorm.Normalize();
                    //contact position is implied with intersecting spheres
                    //calculate seperation velocity
                    Vector3 seperationVelocity = (this.steer.velocity);
                    seperationVelocity = seperationVelocity * contactNorm;
                    //use a coefficient of impulse
                    seperationVelocity *= 1.5f;
                    //apply impulse
                    this.steer.velocity += seperationVelocity;
                    //added code to make things smoother; so my code uses projection as well as impulses.
                    this.steer.position.Z = (itemBound.Min.Z - this.bounding.Radius);
                }
            }
            else
            {
                if (this.steer.position.X > itemBoundCenter.X)
                {
                    Vector3 contactNorm = new Vector3(-1, 0, 0);
                    contactNorm.Normalize();
                    //contact position is implied with intersecting spheres
                    //calculate seperation velocity
                    Vector3 seperationVelocity = (this.steer.velocity);
                    seperationVelocity = seperationVelocity * contactNorm;
                    //use a coefficient of impulse
                    seperationVelocity *= 1.5f;
                    //apply impulse
                    this.steer.velocity += seperationVelocity;
                    //added code to make things smoother; so my code uses projection as well as impulses.
                    this.steer.position.X = (itemBound.Max.X + this.bounding.Radius);
                }
                else
                {
                    Vector3 contactNorm = new Vector3(-1, 0, 0);
                    contactNorm.Normalize();
                    //contact position is implied with intersecting spheres
                    //calculate seperation velocity
                    Vector3 seperationVelocity = (this.steer.velocity);
                    seperationVelocity = seperationVelocity * contactNorm;
                    //use a coefficient of impulse
                    seperationVelocity *= 1.5f;
                    //apply impulse
                    this.steer.velocity += seperationVelocity;
                    //added code to make things smoother; so my code uses projection as well as impulses.
                    this.steer.position.X = (itemBound.Min.X - this.bounding.Radius);
                }
            }
        }

        public void updatePosition(float timeGame)
        {
            steer.velocity += steer.acceleration * timeGame;
            if (steer.velocity.Length() > steer.maxVelocity.Length())
            {
                Vector3 temp = steer.velocity;
                temp.Normalize();
                steer.velocity = temp;
                steer.velocity *= steer.maxVelocity;
            }

            steer.position += steer.velocity * timeGame;

        }

        public void updateOrientation(float timeGame)
        {
            steer.rotation += steer.angularAcceleration;
            if (steer.rotation > steer.maxRotation)
            {
                steer.rotation /= steer.rotation;
                steer.rotation *= steer.maxRotation;
            }

            steer.orientation += steer.rotation * timeGame;
        }

        public void Draw(GraphicsDeviceManager graphics, Matrix view, Matrix projection)
        {
            if (isAlive)
            {
                // DRAW enemy
                // Copy any parent transforms.
                Matrix[] transforms = new Matrix[enemy.Bones.Count];
                enemy.CopyAbsoluteBoneTransformsTo(transforms);

                // Draw the model. A model can have multiple meshes, so loop.
                foreach (ModelMesh mesh in enemy.Meshes)
                {


                    // This is where the mesh orientation is set, as well 
                    // as our camera and projection.
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        effect.EnableDefaultLighting();
                        float scaleAmount = 0.20f;

                        effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateScale(scaleAmount) * Matrix.CreateRotationY(MathHelper.ToRadians(steer.orientation)) * Matrix.CreateTranslation(new Vector3(steer.position.X, steer.position.Y, steer.position.Z));
                        effect.View = view;
                        effect.Projection = projection;
                    }
                    // Draw the mesh, using the effects set above.
                    mesh.Draw();
                }
            }
        }

        // for picking goal position for path finding
        public Vector3 pickClosestTarget()
        {
            float shortestLength = float.MaxValue;
            float distance = 0;
            Vector3 target = new Vector3(0, 0, 0);
            for (int i = 0; i < team.Count; i++)
            {
                distance = (team[i].steer.position - steer.position).Length();
                if (distance < shortestLength && team[i].isAlive)
                {
                    shortestLength = distance;
                    target = team[i].steer.position;
                }
            }
             distance = (player.steer.position - steer.position).Length();
             Random rand1 = new Random();
             int random=rand1.Next() % 3;

            if (distance < shortestLength || random==0)
            {
                shortestLength = distance;
                target = player.steer.position;
            }
            return target;
        }

        //for finding distance to current target
        public float findDistanceToTarget()
        {
            float distance = (pickClosestTarget() - steer.position).Length();

            return distance;
        }


    }
}
